//{==================================================================================================
//! @file "assembler.cpp"
//! @date 2013-11-09
//! @mainpage CPU assembler
//! @author Kirill Shcherbatov <kshcherbatov@gmail.com>
//! @version 2.0
//}===================================================================================================

//****************************************************************************************************
static int use_debug_out = 0;
#define LABEL_MEMORY_MAX_SIZE 16
//****************************************************************************************************

//****************************************************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
//----------------------------------------------------------------------------------------------------
#include "cpu_consts.h"
#include "defines.h"
#include "stack_t.cpp"
//****************************************************************************************************

char assemble_file (char file_path[], double *memory_for_assembled_code, int *memory_for_goto_labels, int *out_used_memory_size)
{
    OUT ("#>>> called assemble_file\n");
    RETURN_ASSERT (memory_for_assembled_code != NULL && out_used_memory_size != NULL);

    FILE *input_file = NULL;
    input_file = fopen (file_path, "r");
    RETURN_ASSERT (input_file != NULL);

    *out_used_memory_size = 0;

    enum SWITCH_MASHINE_STAT
    {
        SEARCH_ASM_COMMAND		= 0,
        SEARCH_DOUBLE_PARAM 	= 1,
        SEARCH_REGISTER_NAME 	= 2
    };
    char buffer[10] = {0},
                      switch_key = SEARCH_ASM_COMMAND,
                      label_define_flag = 0,
                      now_ignore_bug = 0;
    int  fscan_result = 0;

#define BUG_END( MESSAGE )                      \
        free (memory_for_assembled_code);           \
        free (memory_for_goto_labels);				\
        memory_for_assembled_code = NULL;			\
        RETURN_ASSERT (MESSAGE == 0);

    fscan_result = fscanf (input_file, "%s", buffer);
    while (fscan_result  != EOF)
    {
        switch (switch_key)
        {
			case SEARCH_ASM_COMMAND:
			{
				OUT ("# ANALYSE ASM COMMAND, buffer = %s\n", buffer);

				label_define_flag = 0;

				if (buffer[0] == ':')
				{
					int label_number = (int)atoi (buffer + 1);
					OUT ("# FINDED LABEL %d, new value %d\n", label_number, *out_used_memory_size);
					memory_for_goto_labels[label_number] = *out_used_memory_size;
					break;
				}

				#define IF_CMP( ASM_COMMAND, INTERNAL_COMMAND )				    				    \
				if (strcmp (buffer, ASM_COMMAND) == 0)										 	    \
				{																		        	\
						OUT ("# FINDED COMMAND %s\n", buffer);										\
						memory_for_assembled_code[(*out_used_memory_size)++] = INTERNAL_COMMAND;	\
						break;																  		\
				}

				IF_CMP ("end", CPU_CMD_HALT);
				IF_CMP ("dub", CPU_CMD_DUB);
				IF_CMP ("sum", CPU_CMD_SUM);
				IF_CMP ("sub", CPU_CMD_SUB);
				IF_CMP ("mul", CPU_CMD_MUL);
				IF_CMP ("div", CPU_CMD_DIV);
				IF_CMP ("sin", CPU_CMD_SIN);
				IF_CMP ("cos", CPU_CMD_COS);
				IF_CMP ("tan", CPU_CMD_TAN);
				IF_CMP ("asin", CPU_CMD_ASIN);
				IF_CMP ("acos", CPU_CMD_ACOS);
				IF_CMP ("atan", CPU_CMD_ATAN);
				IF_CMP ("log", CPU_CMD_LOG);
				IF_CMP ("sqrt", CPU_CMD_SQRT);
				IF_CMP ("in", CPU_CMD_IN);
				IF_CMP ("ret", CPU_CMD_RET);

				#undef IF_CMP

				#define IF_CMP_NEW_KEY( ASM_COMMAND, INTERNAL_COMMAND, NEW_SW_KEY, VALUE )  			\
				if (strcmp(buffer, ASM_COMMAND) == 0)                                               	\
				{                                                                                   	\
						OUT ("# FINDED COMMAND %s\n", buffer);										    \
						memory_for_assembled_code[(*out_used_memory_size)++] = INTERNAL_COMMAND;    	\
						switch_key = NEW_SW_KEY;                                                   		\
						label_define_flag = VALUE;                                             		    \
						break;                                                                     		\
				}

				IF_CMP_NEW_KEY ("push", CPU_CMD_PUSH_STACK, SEARCH_DOUBLE_PARAM, 0);
				IF_CMP_NEW_KEY ("pop", CPU_CMD_PUSH_REGISTER_FROM_STACK, SEARCH_REGISTER_NAME, 0);
				IF_CMP_NEW_KEY ("out", CPU_CMD_OUT_TEXT, SEARCH_DOUBLE_PARAM, 0);
				IF_CMP_NEW_KEY ("mov", CPU_CMD_PUSH_REGISTER, SEARCH_REGISTER_NAME, 0);

				IF_CMP_NEW_KEY ("jmp", CPU_CMD_JMP, SEARCH_DOUBLE_PARAM,1);
				IF_CMP_NEW_KEY ("je", CPU_CMD_JE, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("jne", CPU_CMD_JNE, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("ja", CPU_CMD_JA, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("jae", CPU_CMD_JAE, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("jb", CPU_CMD_JB, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("jbe", CPU_CMD_JBE, SEARCH_DOUBLE_PARAM, 1);
				IF_CMP_NEW_KEY ("call", CPU_CMD_CALL, SEARCH_DOUBLE_PARAM, 1);

				#undef IF_CMP_NEW_KEY

				fprintf (OUT_DEVICE, "(%d) Invalid assembler command %s\n", *out_used_memory_size, buffer);
				now_ignore_bug = 1;
				break;
			}

			#define COMMAND_IS( COMMAND ) (memory_for_assembled_code[(*out_used_memory_size)-1] == COMMAND)

			case SEARCH_DOUBLE_PARAM:
			{
				OUT ("# ANALYSE SEARCH_DOUBLE_PARAM\n");

				double param_buffer = 0.0;
				int temp = fscanf (input_file, "%lg", &param_buffer);
				OUT ("# READED temp and param_buffer\n");

				if ((COMMAND_IS (CPU_CMD_PUSH_STACK)) && (temp != 1))
				{
					OUT ("# PREV COMMAND IS CPU_CMD_PUSH_STACK\n");
					memory_for_assembled_code[(*out_used_memory_size)-1] = CPU_CMD_PUSH_REGISTER_TO_STACK;
					OUT ("#  IT LOOKS LIKE CPU_CMD_PUSH_REGISTER_TO_STACK\n");
					switch_key = SEARCH_REGISTER_NAME;
					break;
				}

				if ((COMMAND_IS (CPU_CMD_OUT_TEXT)) && (temp != 1))
				{
					OUT ("# COMMAND IS CPU_CMD_OUT_TEXT\n");
					memory_for_assembled_code[(*out_used_memory_size)-1] = CPU_CMD_OUT_VALUE_FROM_STACK;
					OUT ("#  IT LOOKS LIKE CPU_CMD_OUT_VALUE_FROM_STACK\n");
					switch_key = SEARCH_ASM_COMMAND;
					break;
				}

				if (temp == 1)
				{
					OUT ("#  FIND DOUBLE PARAMETER FOR PREV COMMAND PARAM_BUFFER %d\n", (int)param_buffer);
					if (label_define_flag == 1)
					{
						OUT (">> NEED LINK!\n");
						int temp = (int)param_buffer;
						param_buffer = memory_for_goto_labels[(int)param_buffer];

					}
					label_define_flag = 0;
					memory_for_assembled_code[(*out_used_memory_size)++] = param_buffer;
					switch_key = SEARCH_ASM_COMMAND;
					break;
				}

				fprintf (OUT_DEVICE, "(%d) Invalid double parameter (readed %s) %lg for %s\n", *out_used_memory_size, (temp==1) ? "OK" : "BAD",  param_buffer, buffer);
				now_ignore_bug = 1;
				break;
			}

			case SEARCH_REGISTER_NAME:
			{
				int temp = fscanf (input_file, "%s", &buffer);

				if (temp == 1)
				{
					#define ON_CMP( R_NAME, INTERNAL_R_NAME, NEW_SW_KEY )                           \
					if (strcmp( R_NAME, buffer) == 0)                                               \
					{                                                                               \
						OUT ("# FIND REGISTER %s\n", buffer);										\
						memory_for_assembled_code[(*out_used_memory_size)++] = INTERNAL_R_NAME;     \
						switch_key = NEW_SW_KEY;                                                    \
						break;                                                                      \
					}

					if (COMMAND_IS (CPU_CMD_PUSH_REGISTER_TO_STACK) || COMMAND_IS (CPU_CMD_PUSH_REGISTER_FROM_STACK))
					{
						ON_CMP ("ax", AX, SEARCH_ASM_COMMAND)
						else ON_CMP ("bx", BX, SEARCH_ASM_COMMAND)
							else ON_CMP ("cx", CX, SEARCH_ASM_COMMAND)
								else ON_CMP ("dx", DX, SEARCH_ASM_COMMAND)
									else
									{
										fprintf (OUT_DEVICE, "(%d) Invalid register name %s (readed %s)\n",
												 *out_used_memory_size, buffer, (temp==1) ? "OK" : "BAD");
										now_ignore_bug = 1;
										break;
									};
					}

					if (COMMAND_IS (CPU_CMD_PUSH_REGISTER))
					{
						ON_CMP ("ax", AX, SEARCH_DOUBLE_PARAM)
						else ON_CMP ("bx", BX, SEARCH_DOUBLE_PARAM)
							else ON_CMP ("cx", CX, SEARCH_DOUBLE_PARAM)
								else ON_CMP ("dx", CX, SEARCH_DOUBLE_PARAM)
									else
									{
										fprintf (OUT_DEVICE, "(%d) Invalid register name %s (readed %s)\n",
												 *out_used_memory_size, buffer, (temp==1) ? "OK" : "BAD");
										now_ignore_bug = 1;
										break;
									};
					}
				} else
				{
					fprintf (OUT_DEVICE, "(%d) No register name for %s\n",
							 *out_used_memory_size, buffer);
					break;
				};
				#undef ON_CMP
			}

			default: BUG_END("# INVALID ASSEMBLING END-MASHINE STATE\n");
        }
		#undef COMMAND_IS
		if (switch_key == SEARCH_ASM_COMMAND) fscan_result = fscanf (input_file, "%s", buffer);
    }
	#undef BUG_END
    fclose (input_file);
    return now_ignore_bug;
}

int main (int argc, char * argv[])
{
    OUT ( "# %s \n"
          "# (%s)\n"
          "# %s %s:\n\n",
          argv[0], __FILE__, __DATE__, __TIME__ );

    if ((argv[1] != NULL) && (argv[2] != NULL) && (argv[3] != NULL) && strcmp ("-d", argv[3]) == 0) use_debug_out = 1;

    fprintf (OUT_DEVICE, "CPU assembler v 1.0 (author Shcherbatov Kirill) launched.\n");

    char file_path[255] = {0};
    if (argv[1] == NULL)
    {
        fprintf (OUT_DEVICE, "Please enter the path with assembler code:\n");
        scanf (" %s ", &file_path);
    }
    else strcpy (file_path, argv[1]);

    fprintf (OUT_DEVICE, "Trying to lock some memory for compiling...");
    double *memory = NULL;
    memory = (double*)calloc (CPU_MEMORY_SIZE, sizeof (double));
    RETURN_ASSERT (memory != NULL);

    int   *label_memory = NULL;
    label_memory = (int*)calloc (LABEL_MEMORY_MAX_SIZE, sizeof (int));
    RETURN_ASSERT (label_memory != NULL);
    int 	program_size = 0;

    fprintf (OUT_DEVICE, "OK.\n"
             "Start assembling iterations... \n");
    const int repeat_number = 2;
    for (register int i = 0; i < repeat_number; i++)
    {
        if (assemble_file (file_path, memory, label_memory, &program_size) != 0)
        {
			fprintf (OUT_DEVICE, "Compilation error.\n");
			free (memory);
			free (label_memory);
			return 1;
		}
        fprintf (OUT_DEVICE, " Progress [%d / %d]\n", i+1, repeat_number);
    }
    fprintf (OUT_DEVICE, "READY!\n");
    free (label_memory);

    if (argv[2] == NULL)
    {
        fprintf (OUT_DEVICE, "Please enter the path for resulting binary:\n");
        scanf (" %s ", &file_path);
    }
    else strcpy (file_path, argv[2]);

    fprintf (OUT_DEVICE, "Open file to write... ");
    FILE *fout = NULL;
    fout = fopen (file_path, "w");
    RETURN_ASSERT (fout != NULL);

    fprintf (OUT_DEVICE, "OK.\n"
             "Recording... ");

    fprintf (fout, "%d %d ", SHCHERBATOV_KIRILL_PROGRAM_SIGNATURE, program_size);
    for (register int i=0; i < program_size; i++)
        fprintf(fout, "%lg ", memory[i]);

    fprintf (OUT_DEVICE, "OK.\n"
             "Assembling finished successfully.\n");
    fclose(fout);
    free (memory);

    return 0;
}
